﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using CoreEx.Database.Metadata;
using LinFu.Reflection;

namespace CoreEx.Database.Query.Linq.Implementation
{
    [Implements(typeof(IExpressionTranslator))]
    public class ExpressionTranslator : IExpressionTranslator, IInitialize<IServiceRequestResult>
    {
        
        private IExpressionFactory _expressionFactory;
        private ITypeMappingRepository _mappingRepository;
        private IMetadataRepository _metadataRepository;
        private IAliasMap _aliasMap;
        private Type rootType;
        private IServiceContainer _serviceContainer;

        public IExpression Translate(Expression expression)
        {
            rootType = ((LambdaExpression) expression).Parameters[0].Type;
            return Visit(expression);            
        }

    



        protected IExpression Visit(Expression expression)
        {
            switch (expression.NodeType)
            {
                //case ExpressionType.Parameter:
                case ExpressionType.Negate:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.ArrayLength:
                case ExpressionType.Quote:
                case ExpressionType.TypeAs:
                    return VisitUnary((UnaryExpression)expression);
                case ExpressionType.Lambda:
                    return VisitLambda((LambdaExpression) expression);
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.Coalesce:
                case ExpressionType.ArrayIndex:
                case ExpressionType.RightShift:
                case ExpressionType.LeftShift:
                case ExpressionType.ExclusiveOr:
                    return this.VisitBinary((BinaryExpression)expression);
                case ExpressionType.MemberAccess:
                    return this.VisitMemberAccess((MemberExpression)expression);
                case ExpressionType.Constant:
                    return this.VisitConstant((ConstantExpression)expression);
                case ExpressionType.Call:
                    return this.VisitMethodCall((MethodCallExpression)expression);
            }

            return null;
        }
      
        


        protected IExpression VisitUnary(UnaryExpression unaryExpression)
        {
            return Visit(unaryExpression.Operand);
        }


        protected IExpression VisitLambda(LambdaExpression lambda)
        {
            return Visit(lambda.Body);
        }

        protected IExpression VisitBinary(BinaryExpression b)
        {
            switch(b.NodeType)
            {
                case ExpressionType.Equal:
                    return _expressionFactory.Eq(Visit(b.Left), Visit(b.Right));
                case ExpressionType.Or:
                case ExpressionType.OrElse:                    
                    return _expressionFactory.Or(Visit(b.Left), Visit(b.Right));
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    return _expressionFactory.And(Visit(b.Left), Visit(b.Right));            
                case ExpressionType.Add:
                    return _expressionFactory.Add(Visit(b.Left), Visit(b.Right));
                case ExpressionType.Subtract:
                    return _expressionFactory.Subtract(Visit(b.Left), Visit(b.Right));
                case ExpressionType.Multiply:
                    return _expressionFactory.Multiply(Visit(b.Left), Visit(b.Right));
                case ExpressionType.Divide:
                    return _expressionFactory.Divide(Visit(b.Left), Visit(b.Right));


                default:
                    return null;
            }                        
        }

        protected IExpression VisitMemberAccess(MemberExpression memberExpression)
        {
            Type elementType = TypeSystem.GetElementType(memberExpression.Type);
            if (typeof (IEnumerable).IsAssignableFrom(memberExpression.Type) && elementType != typeof(string))
            {
                return _expressionFactory.Table(GetTableName(elementType));
            }

            return _expressionFactory.Column(_aliasMap.GetCurrentAlias(GetTableName(memberExpression.Member.DeclaringType)),
                                             GetColumnName(memberExpression.Member));

        }

        protected IExpression VisitConstant(ConstantExpression constantExpression)
        {
            return _expressionFactory.Constant(constantExpression.Value);
        }
        
        protected IExpression VisitMethodCall(MethodCallExpression methodCallExpression)
        {
            switch(methodCallExpression.Method.Name)
            {
                case "StartsWith":
                    return _expressionFactory.StartsWith(Visit(methodCallExpression.Object), Visit(methodCallExpression.Arguments[0]));
                case "Any":
                    {
                        var member = ((MemberExpression) methodCallExpression.Arguments[0]).Member;
                        
                        var projector = _serviceContainer.GetService<IExpressionProjector>();
                        var subQuery = projector.CreateProjection(methodCallExpression.Arguments[1]);
                        IExpression expression = Visit(methodCallExpression.Arguments[1]);
                        subQuery.Where(expression);
                        return _expressionFactory.Exists(subQuery);
                    }
                case "Sum":
                    {
                        var outerTableName =
                            GetTableName(((MemberExpression) methodCallExpression.Arguments[0]).Expression.
                                             Type);                            
                        var outerAlias =
                            ((ParameterExpression) ((MemberExpression) methodCallExpression.Arguments[0]).Expression).
                                Name;

                        var tableName = ((ITableExpression) Visit(methodCallExpression.Arguments[0])).TableName;
                        var tableAlias = ((LambdaExpression) methodCallExpression.Arguments[1]).Parameters[0].Name;
                        
                        
                        var tableInfo = _metadataRepository.Tables.Where(t => t.TableName == tableName).First();
                        
                        var outerTableInfo = _metadataRepository.Tables.Where(t => t.TableName == outerTableName).First();

                        IForeignKeyInfo foreignKeyInfo =
                           tableInfo.ForeignKeys.Where(fk => fk.PrimaryTable == outerTableInfo).First();

                        var subQueryExpression = _expressionFactory.Select(e => e.Sum(Visit(methodCallExpression.Arguments[1])))
                            .From(e => e.Alias(Visit(methodCallExpression.Arguments[0]), ((LambdaExpression)methodCallExpression.Arguments[1]).Parameters[0].Name));
                        foreach (var foreignkeyColumnInfo in foreignKeyInfo.Columns)
                        {
                            subQueryExpression.Where(
                                _expressionFactory.Column(tableAlias, foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                                _expressionFactory.Column(outerAlias, foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));
                        }                                                                                              
                        return subQueryExpression;                        
                    }                    
                    
                default:
                    return null;
            }
            
           
        }


        private string GetTableName(Type type)
        {
            return _mappingRepository[type].TableInfo.TableName;    
        }
            

        private string GetColumnName(MemberInfo memberInfo)
        {
            return
                _mappingRepository[memberInfo.DeclaringType].ColumnMappings[(PropertyInfo) memberInfo].ColumnInfo.
                    ColumnName;
        }
      

        public void Initialize(IServiceRequestResult source)
        {
             _expressionFactory = source.Container.GetService<IExpressionFactory>();
            _metadataRepository = source.Container.GetService<IMetadataRepository>(source.ServiceName);
            _mappingRepository = source.Container.GetService<ITypeMappingRepository>(source.ServiceName);
            _aliasMap = source.Container.GetService<IAliasMap>();
            _serviceContainer = source.Container;
        }
        
    }
}